#!/usr/bin/env python3
# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2020 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
#          http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
# ----------------------------------------------------------------------------
# Description  : gs_preinstall is a utility to create an installation
# environment for a cluster.
#############################################################################

import os
import pwd
import sys
import grp
import subprocess
from gspylib.common.CheckPythonVersion import checkPythonVersion
checkPythonVersion()

from gspylib.common.DbClusterInfo import dbClusterInfo
from gspylib.common.GaussLog import GaussLog
from gspylib.common.Common import DefaultValue
DefaultValue.doConfigForParamiko()
from gspylib.common.ErrorCode import ErrorCode
from gspylib.common.ParallelBaseOM import ParallelBaseOM
from gspylib.common.ParameterParsecheck import Parameter
from impl.preinstall.OLAP.PreinstallImplOLAP import PreinstallImplOLAP
from gspylib.threads.SshTool import SshTool
from domain_utils.cluster_file.cluster_config_file import ClusterConfigFile
from domain_utils.cluster_file.cluster_dir import ClusterDir
from domain_utils.cluster_file.profile_file import ProfileFile
from domain_utils.cluster_os.cluster_user import ClusterUser
from base_utils.os.net_util import NetUtil
from domain_utils.domain_common.cluster_constants import ClusterConstants
from base_utils.os.user_util import UserUtil

#############################################################################
# Global variables
#############################################################################
userNameFirtChar = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']


class Preinstall(ParallelBaseOM):
    def __init__(self):
        ParallelBaseOM.__init__(self)
        self.password = ""
        self.envParams = []
        self.rootUser = ""
        self.rootPasswd = ""
        self.createUserSshTrust = True
        self.clusterToolPath = ""
        self.needFixOwnerPaths = []
        self.preMode = False
        self.skipOSSet = False
        self.skipHostnameSet = False
        self.passwordsec = ""
        self.corePath = ""
        self.is_new_root_path = False
        self.ips = ""
        self.root_ssh_agent_flag = False
        self.user_ssh_agent_flag = False

    def usage(self):
        """
gs_preinstall is a utility to create an installation environment for a cluster.

Usage:
    gs_preinstall -? | --help
    gs_preinstall -V | --version
    gs_preinstall -U USER -G GROUP -X XMLFILE
      [-L]  [--skip-os-set] [--env-var="ENVVAR" [...]]
      [--sep-env-file=ENVFILE] [--skip-hostname-set] [-l LOGFILE]
      [--non-interactive]

General options:
    -U                                 Cluster user.
    -G                                 Group of the cluster user.
    -X                                 Path of the XML configuration file.
    -L                                 Only perform preinstallation on local
                                       nodes.
        --skip-os-set                  Whether to skip OS parameter setting.
                                       (The default value is set.)
        --env-var="ENVVAR"             OS user environment variables.
        --sep-env-file=ENVFILE       Path of the MPP environment file.
        --skip-hostname-set            Whether to skip hostname setting.
                                       (The default value is set.)
    -l                                 Path of log file.
    -?, --help                         Show help information for this
                                       utility, and exit the command line mode.
    -V, --version                      Show version information.
        --non-interactive              Pre-execution of non-secure mode.
                                     If it is not specified, you can choose
                                     whether create the SSH trust for root
                                     user or cluster user.
                                     If it is specified, you must ensure the
                                     SSH trust for root user and cluster
                                     user have been created.
        """
        print(self.usage.__doc__)

    # get parameter from command
    def parseCommandLine(self):
        """
        function: Parse command line and save to global variable
        input: NA
        output: NA
        """
        # init the ParaObj
        ParaObj = Parameter()
        ParaDict = ParaObj.ParameterCommandLine("preinstall")
        # parameter -h or -?
        if (ParaDict.__contains__("helpFlag")):
            self.usage()
            sys.exit(0)

        # Resolves command line arguments
        # parameter -U
        if (ParaDict.__contains__("user")):
            self.user = ParaDict.get("user")
            DefaultValue.checkPathVaild(self.user)
        # parameter -G
        if (ParaDict.__contains__("group")):
            self.group = ParaDict.get("group")
        # parameter -X
        if (ParaDict.__contains__("confFile")):
            self.xmlFile = ParaDict.get("confFile")
        # parameter -L
        if (ParaDict.__contains__("localMode")):
            self.localMode = ParaDict.get("localMode")
        # parameter -l
        if (ParaDict.__contains__("logFile")):
            self.logFile = ParaDict.get("logFile")
        # parameter --env-var
        if (ParaDict.__contains__("envparams")):
            self.envParams = ParaDict.get("envparams")
        # parameter --sep-env-file
        if (ParaDict.__contains__("mpprcFile")):
            self.mpprcFile = ParaDict.get("mpprcFile")
            DefaultValue.checkPathVaild(self.mpprcFile)
        # parameter --skip-hostname-set
        if (ParaDict.__contains__("skipHostnameSet")):
            self.skipHostnameSet = ParaDict.get("skipHostnameSet")
        # parameter --skip-os-set
        if (ParaDict.__contains__("skipOSSet")):
            self.skipOSSet = ParaDict.get("skipOSSet")
        # parameter --non-interactive
        if (ParaDict.__contains__("preMode")):
            self.preMode = ParaDict.get("preMode")

    def checkUserParameter(self):
        """
        """
        if (self.user == ""):
            GaussLog.exitWithError(
                ErrorCode.GAUSS_500["GAUSS_50001"] % 'U' + ".")
        elif (":" in self.user):
            GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50004"] % 'U')

        # check if user exists
        cmd = "cat /etc/passwd|grep -v nologin|grep -v halt|" \
              "grep -v shutdown|awk -F: '{ print $1 }'|" \
              " grep '^%s$' 2>/dev/null" % self.user
        status = subprocess.getstatusoutput(cmd)[0]
        if status == 0:
            if pwd.getpwnam(self.user).pw_uid == 0:
                # user exists and uid is 0, exit.
                GaussLog.exitWithError(ErrorCode.GAUSS_503["GAUSS_50302"])

        # check the local user and the localmode,
        # if user not exist exit with error
        if (self.localMode):
            try:
                DefaultValue.getUserId(self.user)
            except Exception as e:
                GaussLog.exitWithError(str(e))


    def checkUserAndGroup(self):
        """
        """
        if (self.localMode):
            usergroup = grp.getgrgid(pwd.getpwnam(self.user).pw_gid).gr_name
            if (self.group != usergroup):
                GaussLog.exitWithError(ErrorCode.GAUSS_503["GAUSS_50305"]
                                       + "User:Group[%s:%s]"
                                       % (self.user, self.group))

    def check_config_content(self, g_nodeInfo):
        UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("gaussdbAppPath", self.xmlFile))
        UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("gaussdbToolPath", self.xmlFile))
        UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("tmpMppdbPath", self.xmlFile))
        UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("gaussdbLogPath", self.xmlFile))
        UserUtil.check_path_owner(ClusterConfigFile.getOneClusterConfigItem("corePath", self.xmlFile))

        # check cm
        UserUtil.check_path_owner(g_nodeInfo.cmDataDir)
        for cmaInst in g_nodeInfo.cmagents:
            UserUtil.check_path_owner(cmaInst.datadir)
        for cmsInst in g_nodeInfo.cmservers:
            UserUtil.check_path_owner(cmsInst.datadir)

        # check dn
        for dnInst in g_nodeInfo.datanodes:
            UserUtil.check_path_owner(dnInst.datadir)
            if (len(dnInst.ssdDir) != 0):
                UserUtil.check_path_owner(dnInst.ssdDir)
        # check dn xlog
        for dnInst in g_nodeInfo.datanodes:
            if dnInst.xlogdir != '':
                UserUtil.check_path_owner(dnInst.xlogdir)


    def checkEnvValueParameter(self):
        """
        """
        for param in self.envParams:
            # check environmental variables vaild
            illegal = ["|", ";", "&", "$", ">", "<", "`", "\\", "!", "\n"]
            if any(ill_char in param for ill_char in illegal):
                GaussLog.exitWithError(
                    ErrorCode.GAUSS_500["GAUSS_50004"] % "-env-var" +
                    " There are illegal characters in the parameter.")

    def checkLogFile(self):
        """
        """
        if (self.logFile == ""):
            self.logFile = self.getPreOMLogPath(
                ClusterConstants.PREINSTALL_LOG_FILE, self.xmlFile)
        if (not os.path.isabs(self.logFile)):
            GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50213"]
                                   % self.logFile)
        UserUtil.check_path_owner(self.logFile)


    def checkMpprcFile(self):
        """
        """
        if (self.mpprcFile == ""):
            return

        if (not os.path.isabs(self.mpprcFile)):
            GaussLog.exitWithError(ErrorCode.GAUSS_512["GAUSS_51206"]
                                   % self.mpprcFile)

        # check mpprc file path
        mpprcFilePath = os.path.normpath(self.mpprcFile)
        if os.path.islink(mpprcFilePath):
            GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_51251"] % mpprcFilePath)
        if (mpprcFilePath == "/home/%s" % self.user):
            GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50004"] % \
                                   '-sep-env-file' + " The file [%s] can not"
                                                     " be a reserved home "
                                                     "directory."
                                   % self.mpprcFile)
        if (os.path.isdir(self.mpprcFile)):
            GaussLog.exitWithError(ErrorCode.GAUSS_500["GAUSS_50004"] % \
                                   '-sep-env-file' + " The file [%s] can not "
                                                     "be a directory."
                                   % self.mpprcFile)

        ProfileFile.checkMpprcFileChange(self.mpprcFile, "", self.mpprcFile)
        (checkstatus, checkoutput) = ProfileFile.check_env_file(self.mpprcFile)
        if (not checkstatus):
            if (self.mpprcFile != ""):
                envfile = self.mpprcFile + " and /etc/profile"
            else:
                envfile = "/etc/profile and ~/.bashrc"
            GaussLog.exitWithError(ErrorCode.GAUSS_518["GAUSS_51808"] % \
                                   checkoutput + "Please check %s." % envfile)

    def checkParameter(self):
        """
        function: Check parameter from command line
        input: NA
        output: NA
        """
        ClusterUser.checkGroupParameter(self.user, self.group)
        # remove HOST_IP info with /etc/profile and environ
        cmd = "sed -i '/^export[ ]*HOST_IP=/d' /etc/profile"
        (status, output) = subprocess.getstatusoutput(cmd)
        if status != 0:
            GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50205"]
                                % ClusterConstants.ETC_PROFILE + "The cmd is %s" % cmd)
        if "HOST_IP" in os.environ.keys():
            os.environ.pop("HOST_IP")

        # check config file
        ClusterConfigFile.checkConfigFile(self.xmlFile)
        # check user info
        self.checkUserParameter()
        # check user group match
        self.checkUserAndGroup()
        self.initClusterInfo()
        # check config content
        hostName = NetUtil.GetHostIpOrName()
        g_nodeInfo = self.clusterInfo.getDbNodeByName(hostName)
        if (g_nodeInfo is None):
            GaussLog.exitWithError(ErrorCode.GAUSS_516["GAUSS_51620"] % "local" +
                                " It is not a host name %s." % hostName)

        # check env-val
        self.checkEnvValueParameter()
        # check mpprc file
        self.checkMpprcFile()

        # check log file
        self.checkLogFile()
    
    # set LD_LIBRARY_PATH add local lib
    def setLibPath(self):
        package_path = os.path.dirname(os.path.realpath(__file__))
        ld_path = package_path + "/gspylib/clib"
        rerun = True
 
        if 'LD_LIBRARY_PATH' not in os.environ:
            os.environ['LD_LIBRARY_PATH'] = ld_path
        elif not os.environ.get('LD_LIBRARY_PATH').startswith(ld_path):
            os.environ['LD_LIBRARY_PATH'] = \
                ld_path + ":" + os.environ['LD_LIBRARY_PATH']
        else:
            rerun = False

        if rerun:
            try:
                os.execve(os.path.realpath(__file__), sys.argv, os.environ)
            except Exception as e:
                GaussLog.exitWithError(str(e))

    # decompress version.cfg from bz2
    def decompressVersioncfg(self):
        package_path = os.path.dirname(os.path.realpath(__file__))
        toolpath = package_path + "/../"
        cmd = "cd " + toolpath + " && tar -xpf `head -1 version.cfg`*.tar.bz2 ./version.cfg ./bin/encrypt " \
                                 "&& mv ./bin/encrypt ./script/gspylib/clib && rm -rf ./bin"
        (status, _) = subprocess.getstatusoutput(cmd)
        if status != 0:
            cmd = "cd " + toolpath + " && tar -xpf `ls openGauss*.tar.bz2|tail -1` ./version.cfg" \
                                     " ./bin/encrypt && mv ./bin/encrypt ./script/gspylib/clib" \
                                     " && rm -rf ./bin"
            (status, output) = subprocess.getstatusoutput(cmd)
            if status != 0:
                GaussLog.exitWithError(ErrorCode.GAUSS_502["GAUSS_50217"] % "version.cfg"
                                       + "The cmd is %s. " % cmd + "The output is %s." % output)

    # init global variables
    def initGlobals(self):
        """
        function: init global parameters
        input: NA
        output: NA
        """
        # init the log file
        self.initLogger("gs_preinstall")

        # get the clusterToolPath
        self.clusterToolPath = ClusterDir.getPreClusterToolPath(self.xmlFile)
        temp_nodes = ClusterConfigFile.getOneClusterConfigItem("nodeNames", self.xmlFile)
        if len(temp_nodes.split(',')) < 2:
            self.isSingle = True
        os.environ[ClusterConstants.TOOL_PATH_ENV] = self.clusterToolPath

        self.logger.log("Parsing the configuration file.", "addStep")
        try:
            # parse the configuration file
            self.sshTool = SshTool(self.clusterInfo.getClusterNodeNames(),
                                   self.logFile,
                                   DefaultValue.TIMEOUT_PSSH_PREINSTALL)

        except Exception as e:
            self.logger.logExit(str(e))

        # check the local hostname
        if NetUtil.GetHostIpOrName() not in \
                self.clusterInfo.getClusterNodeNames():
            self.logger.logExit(ErrorCode.GAUSS_516["GAUSS_51619"]
                                % NetUtil.GetHostIpOrName())
        self.logger.log("Successfully parsed the configuration file.",
                        "constant")

    # check expect for cm/create trust
    def check_expect(self):
        """
        function: check expect
        input: NA
        output: NA
        """
        temp_nodes = ClusterConfigFile.getOneClusterConfigItem("nodeNames", self.xmlFile)
        if len(temp_nodes.split(',')) > 1:
            cmd = "echo exit|expect"
            (status, _) = subprocess.getstatusoutput(cmd)
            if status != 0:
                GaussLog.exitWithError(ErrorCode.GAUSS_514["GAUSS_51405"] % "expect")

    def getPreOMLogPath(self, logName, xml):
        """
        function: get the OM log path
        input: logName, xml
        output: fullLogPath
        """
        try:
            fullLogPath = ""
            # get the log path
            configedLogPath = ClusterConfigFile.getOneClusterConfigItem("gaussdbLogPath",
                                                           xml)
            DefaultValue.checkPathVaild(configedLogPath)
            # check gaussdbLogPath is not null
            if configedLogPath == "":
                fullLogPath = "%s/%s/om/%s" % (
                ClusterConstants.GAUSSDB_DIR, self.user, logName)
            else:
                fullLogPath = "%s/%s/om/%s" % (
                os.path.normpath(configedLogPath), self.user, logName)
            UserUtil.check_path_owner(fullLogPath)
            return fullLogPath
        except Exception as e:
            GaussLog.exitWithError(str(e))



    def change_lib_path(self):
        """
        if gs_preinstall current path is /root/gauss_om/username,
         so change its lib path
        :return:
        """
        gsom_path = os.path.realpath(
                    os.path.join(os.path.realpath(__file__), "../../../"))
        package_path = os.path.dirname(os.path.realpath(__file__))
        lib_path = os.path.join(package_path, "lib")
        sys.path.insert(0, lib_path)
        if gsom_path == DefaultValue.ROOT_SCRIPTS_PATH:
            self.is_new_root_path = True


def clearHistTimeFormat():
    cmd = "sed -i '/HISTTIMEFORMAT=/d' /etc/profile"
    (status, output) = subprocess.getstatusoutput(cmd)
    if status != 0:
        GaussLog.exitWithError("Clear HISTTIMEFORMAT from /etc/profile "
                               "failed.\nError: %s\nThe cmd is: %s\n" %
                               (output,cmd))

if __name__ == '__main__':
    """
    main function
    """
    # check if user is root
    if os.getuid() != 0:
        GaussLog.exitWithError(ErrorCode.GAUSS_501["GAUSS_50104"])
    clearHistTimeFormat()
    try:
        # Objectize class
        preinstall = Preinstall()
        # set LD_LIBRARY_PATH
        preinstall.setLibPath()
        # parse cmd lines
        preinstall.parseCommandLine()
        # check parameters
        preinstall.checkParameter()
        # check expect
        preinstall.check_expect()
        # init global variables
        preinstall.initGlobals()
        # decompress version.cfg
        preinstall.decompressVersioncfg()
        preinstall.change_lib_path()
        impl = PreinstallImplOLAP(preinstall)
        # Perform the whole extand process
        impl.run()
    except Exception as e:
        GaussLog.exitWithError(str(e))
